Skip to content

Fix left+right tab-stop column layout (signature blocks) - #14

Closed
jmcopeland wants to merge 3 commits into
extend-hq:mainfrom
jmcopeland:fix/tab-stop-right-tab-wrap
Closed

Fix left+right tab-stop column layout (signature blocks)#14
jmcopeland wants to merge 3 commits into
extend-hq:mainfrom
jmcopeland:fix/tab-stop-right-tab-wrap

Conversation

@jmcopeland

Copy link
Copy Markdown
Contributor

Summary

Fixes #13 — two-column signature blocks (a left tab stop + a right-aligned tab stop in the same paragraph) render with incorrect wrapping/positioning vs Word.

Root cause

paragraphAnchoredTabLayout(...) gates whether a paragraph uses the anchored tab renderer (buildAnchoredTabZones, which positions zones at the tab-stop coordinates). It bailed out for the exact signature-block pattern:

if (hasLeft && (hasCenter || hasRight)) return "none";

So a left tab + right tab paragraph fell back to the plain tab path, which resolves every tab as left-aligned (resolveNextTabStopPx just advances the cursor to the next stop). The w:val="right" tab was rendered as a left tab → wrong wrap.

Fix

Add a "left-right" anchored mode: when a paragraph has a left tab and a right tab (no center) with ≥ 2 tab characters, render a 3-zone CSS grid — zone 0 at the left margin, zone 1 left-aligned at the left tab, zone 2 right-aligned at the right tab — reusing buildAnchoredTabZones(3) and the same grid-anchoring approach as the existing center-right layout. Threaded the new mode through both paragraph-render paths, the pretext line-count path, and the special-tab-layout gate.

Test

tests/unit/left-right-tab-columns.test.ts — a renderToStaticMarkup regression asserting a left+right paragraph renders via data-docx-tab-layout="left-right" with three zones (fails on main, passes here).

Full unit suite: 475 passed, 15 skipped, 0 failures — no regressions to the existing center / right / center-right / header-footer tab layouts.

Repro doc: https://github.com/jmcopeland/react-docx/raw/repro/tab-stop-column-wrap/repros/tab-stop-column-wrap.docx

Paragraphs mixing a left tab stop with a right-aligned tab stop — the classic
two-column signature block — were excluded from the anchored tab renderer by
the `hasLeft && (hasCenter || hasRight)` guard and fell back to the plain
tab path, which treats every tab as left-aligned. The right tab was rendered
as a left tab, mis-wrapping the columns vs Word.

Add a `left-right` anchored mode: when a paragraph has a left tab + a right
tab (no center) and >= 2 tab characters, render a 3-zone grid (margin /
left-tab / right-tab-right-aligned), reusing buildAnchoredTabZones and the same
grid-anchoring approach as center-right.

Fixes extend-hq#13.

Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

@jmcopeland is attempting to deploy a commit to the Extend Team on Vercel.

A member of the Team first needs to authorize it.

@jmcopeland
jmcopeland marked this pull request as draft July 13, 2026 21:35
jmcopeland and others added 2 commits July 13, 2026 14:46
Refine the left-right anchored layout: collapse the trailing right tab (Word
drops it once the column content runs past it) and render everything after the
first tab as a single left-aligned, normally-wrapping right column, instead of
a fixed right-aligned zone that overlapped the column text. Verified in the
playground against the signature-block repro.

Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A line break inside a tab-column paragraph is a shared row boundary: it must
start a new line in every column and return to the first column. Tokenize the
anchored zone builder on both tabs and newlines (newline => <br> into all zones
+ reset to column 0), and render the left-right zones as blocks so those breaks
render. This makes the common two-line signature block (party name / 'On behalf
of...') lay out as aligned two-column rows. Regression test covers the two-row
case; full unit suite green.

Co-Authored-By: Justin Copeland <justin.copeland@legalontech.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jmcopeland

Copy link
Copy Markdown
Contributor Author

Scope note: this PR fixes the left + right tab-stop anchored two-column case (verified across a multi-variant signature-block repro; the left-right variant now renders aligned two-column rows including the line-break case).

Two adjacent plain tab-path mis-positioning bugs surfaced during validation but are out of scope here and tracked separately in #15: (1) a wrapped/second line's tab lands short of the stop when its preceding text is longer (variants using a left-tab-only paragraph or two tab-separated paragraphs), and (2) single-tab rows (the ____ [tab] ____ signature line) sit ~one position off. Those route through the non-anchored resolver, not the anchored renderer this PR touches.

jmcopeland added a commit to jmcopeland/react-docx that referenced this pull request Jul 14, 2026
@jmcopeland

Copy link
Copy Markdown
Contributor Author

Superseding this in favor of #16. The left-right anchored grid here correctly handled the explicit-line-break signature block, but it mishandled the far more common no-line-break form (a run that wraps to the margin, e.g. the McKinsey signature block) by mashing the tab-delimited segments together. #16 instead makes the plain-tab path alignment-aware (right/center stops) on top of canvas-accurate measurement, which renders both the line-break and wrap forms correctly and also covers genuine right-aligned trailers. Closing; the work continues in #16.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tab-stop columns (left + right-aligned tabs) wrap/position incorrectly vs Word

1 participant